#!/usr/bin/env node

'use strict';

const child_process = require('child_process');
const crypto = require('crypto');
const fs = require('fs');
const os = require('os');
const path = require('path');

const SearchSource = require('jest').SearchSource;
const TestRunner = require('jest').TestRunner;
const TestWatcher = require('jest').TestWatcher;

const createHasteContext = require('jest-runtime').createHasteContext;
const readConfig = require('jest-config').readConfig;

const argv = {};
const root = path.normalize(path.join(__dirname, '..', '..'));
const testPathPattern = '';

function wrapRunnerFile(runnerPath) {
  const filename = path.join(
    os.tmpdir(),
    'test-runner-' + crypto.randomBytes(8).toString('hex') + '.js'
  );
  fs.writeFileSync(
    filename,
    `
      'use strict';
      var runnerPath = ${JSON.stringify(runnerPath)};
      var wrap = require(${JSON.stringify(__filename)}).wrapRunner;
      module.exports = wrap(runnerPath);
    `
  );
  return filename;
}

function wrapRunner(originalPath) {
  const original = require(originalPath);
  // Assuming originalPath is .../jest-jasmine2/build/index.js
  const JasmineReporter = require(
    path.join(path.dirname(originalPath), 'reporter.js')
  );

  // For each spec, we store whether there was any expectDev() failure. This
  // relies on the results being returned in the same order as they are run.
  const hadDevFailures = [];
  let environment;

  const oldSpecStarted = JasmineReporter.prototype.specStarted;
  JasmineReporter.prototype.specStarted = function(result) {
    oldSpecStarted.apply(this, arguments);

    environment.global.__suppressDevFailures = true;
    environment.global.__hadDevFailures = false;
  };

  const oldSpecDone = JasmineReporter.prototype.specDone;
  JasmineReporter.prototype.specDone = function(result) {
    oldSpecDone.apply(this, arguments);

    environment.global.__suppressDevFailures = false;
    hadDevFailures.push(environment.global.__hadDevFailures);
  };

  return function runner(config, env, runtime, testPath) {
    environment = env;
    return original(config, env, runtime, testPath)
      .then((results) => {
        results.failureMessage = null;
        hadDevFailures.forEach((hadFailures, i) => {
          results.testResults[i].hadDevFailures = hadFailures;
        });
        hadDevFailures.length = 0;
        return results;
      });
  };
}

function runJest(maxWorkers) {
  return readConfig(argv, root)
    .then(({ config }) => {
      config = Object.assign({}, config, {
        testRunner: wrapRunnerFile(config.testRunner),
      });
      return createHasteContext(config, {}).then((hasteMap) => {
        const source = new SearchSource(hasteMap, config);
        return source.getTestPaths({testPathPattern})
          .then((data) => {
            const runner = new TestRunner(
              hasteMap,
              config,
              {
                maxWorkers: maxWorkers,
                getTestSummary: () => 'You did it!'
              }
            );
            const watcher = new TestWatcher({isWatchMode: false});
            return runner.runTests(data.paths, watcher);
          });
      });
    });
}

function formatResults(runResults, predicate) {
  const formatted = [];
  runResults.testResults.forEach((fileResult) => {
    const filePath = path.relative(root, fileResult.testFilePath);
    // on windows, we still want to output forward slashes
    const unixFilePath = filePath.replace(/\\/g, '/');
    const tests = fileResult.testResults.filter(
      (test) => predicate(fileResult, test)
    );
    if (tests.length) {
      const lines = [unixFilePath].concat(tests.map((test) => '* ' + test.title));
      formatted.push(lines.join('\n'));
    }
  });
  formatted.sort();
  return formatted.join('\n\n');
}

function recordTests(maxWorkers, trackFacts) {
  process.env.REACT_DOM_JEST_USE_FIBER = true;
  runJest(maxWorkers)
    .then((runResults) => {
      const passing = formatResults(
        runResults,
        (file, test) => test.status === 'passed' && !test.hadDevFailures
      );
      const passingExceptDev = formatResults(
        runResults,
        (file, test) => test.status === 'passed' && test.hadDevFailures
      );
      const failing = formatResults(
        runResults,
        (file, test) => test.status === 'failed'
      );
      fs.writeFileSync(
        path.join(__dirname, 'tests-passing.txt'),
        passing + '\n'
      );
      fs.writeFileSync(
        path.join(__dirname, 'tests-passing-except-dev.txt'),
        passingExceptDev + '\n'
      );
      fs.writeFileSync(
        path.join(__dirname, 'tests-failing.txt'),
        failing + '\n'
      );

      if (trackFacts) {
        const fact = runResults.numPassedTests + '/' + (runResults.numPassedTests + runResults.numFailedTests);
        // TODO: Shelling out here is silly.
        child_process.spawnSync(
          process.execPath,
          [
            path.join(__dirname, '../facts-tracker/index.js'),
            'fiber-tests',
            fact,
          ],
          {
            stdio: 'inherit',
          }
        );
      }
    });
}

if (require.main === module) {
  const argv = require('yargs')
    .demand(0, 0)
    .number('max-workers')
    .describe('max-workers', 'Number of workers to use for jest.')
    .default('max-workers', Math.max(os.cpus().length - 1, 1))
    .boolean('track-facts')
    .describe('track-facts', 'Use facts-tracker to record passing tests.')
    .strict()
    .help()
    .argv;
  recordTests(argv.maxWorkers, argv.trackFacts);
}

module.exports = {
  wrapRunner,
};
